{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 基于矩阵分解的协同过滤"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入必要包\n",
    "import time\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "from numpy.random import random\n",
    "import pickle"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>user</th>\n",
       "      <th>song</th>\n",
       "      <th>play_count_total_x</th>\n",
       "      <th>score</th>\n",
       "      <th>play_count_total_y</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>37078</td>\n",
       "      <td>e6e0f68e948d7bcbf2ed9c4506a40a139a5e7bc7</td>\n",
       "      <td>SOYYKLS12A8C134802</td>\n",
       "      <td>681</td>\n",
       "      <td>0.002937</td>\n",
       "      <td>681</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>32150</td>\n",
       "      <td>a18aa09c5b8a1c03d03cdf6d8eb11c2bf5b907cd</td>\n",
       "      <td>SOXQYSC12A6310E908</td>\n",
       "      <td>807</td>\n",
       "      <td>0.003717</td>\n",
       "      <td>807</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2893</td>\n",
       "      <td>da3890400751de76f0f05ef0e93aa1cd898e7dbc</td>\n",
       "      <td>SOIROON12A6701E0B8</td>\n",
       "      <td>592</td>\n",
       "      <td>0.003378</td>\n",
       "      <td>592</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>14124</td>\n",
       "      <td>d04eed168e8e31d9d05cfca98cf08a3abf7bd9f4</td>\n",
       "      <td>SOPJLFV12A6701C797</td>\n",
       "      <td>487</td>\n",
       "      <td>0.006160</td>\n",
       "      <td>487</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>13014</td>\n",
       "      <td>a41d3edbc2798b6800fe15845a979150eb244b85</td>\n",
       "      <td>SOHFJAQ12AB017E4AF</td>\n",
       "      <td>956</td>\n",
       "      <td>0.002092</td>\n",
       "      <td>956</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                           user                song  \\\n",
       "37078  e6e0f68e948d7bcbf2ed9c4506a40a139a5e7bc7  SOYYKLS12A8C134802   \n",
       "32150  a18aa09c5b8a1c03d03cdf6d8eb11c2bf5b907cd  SOXQYSC12A6310E908   \n",
       "2893   da3890400751de76f0f05ef0e93aa1cd898e7dbc  SOIROON12A6701E0B8   \n",
       "14124  d04eed168e8e31d9d05cfca98cf08a3abf7bd9f4  SOPJLFV12A6701C797   \n",
       "13014  a41d3edbc2798b6800fe15845a979150eb244b85  SOHFJAQ12AB017E4AF   \n",
       "\n",
       "       play_count_total_x     score  play_count_total_y  \n",
       "37078                 681  0.002937                 681  \n",
       "32150                 807  0.003717                 807  \n",
       "2893                  592  0.003378                 592  \n",
       "14124                 487  0.006160                 487  \n",
       "13014                 956  0.002092                 956  "
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_train=pd.read_csv('triplet_dataset_sub_train.csv',index_col=0)\n",
    "df_train.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "#读取用户-歌曲\n",
    "user_item_dict=pickle.load(open('user_item_dict.pkl','rb'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "训练SVD模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "#隐含变量的维数\n",
    "K = 40\n",
    "\n",
    "#item和用户的偏置项\n",
    "#bi = np.zeros((df_train['song'].unique(),1))    \n",
    "#bu = np.zeros((df_train['user'].unique(),1))   \n",
    "\n",
    "bi={}\n",
    "bu={}\n",
    "for i in df_train['song'].unique():\n",
    "    bi[i]=0\n",
    "for u in df_train['user'].unique():\n",
    "    bu[u]=0\n",
    "\n",
    "\n",
    "#item和用户的隐含向量\n",
    "#qi =  np.zeros((df_train['song'].unique(),K))    \n",
    "#pu =  np.zeros((df_train['user'].unique(),K))   \n",
    "\n",
    "qi={}\n",
    "pu={}\n",
    "\n",
    "for uid in df_train['user'].unique():  #对每个用户\n",
    "    pu[uid] = np.reshape(random((K,1))/10*(np.sqrt(K)),K)\n",
    "       \n",
    "for iid in df_train['song'].unique():  #对每个item\n",
    "    qi[iid] = np.reshape(random((K,1))/10*(np.sqrt(K)),K)\n",
    "\n",
    "#所有用户的平均打分\n",
    "mu = df_train['score'].mean()  #average rating"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 定义基于svd推荐的评分函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "#定义用户对歌曲评分预测函数\n",
    "\n",
    "#定义用户对歌曲评分预测函数\n",
    "def svd_pred(uid, iid):  \n",
    "    score = mu + bi[iid] + bu[uid] + np.sum(qi[iid]* pu[uid])  \n",
    "        \n",
    "    #将打分范围控制在1-5之间\n",
    "    #if score>5:  \n",
    "        #score = 5  \n",
    "    #elif score<1:  \n",
    "        #score = 1  \n",
    "        \n",
    "    return score  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/opt/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:12: DeprecationWarning: time.clock has been deprecated in Python 3.3 and will be removed from Python 3.8: use time.perf_counter or time.process_time instead\n",
      "  if sys.path[0] == '':\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The 0-th  step is running\n",
      "the rmse of this step on train data is  0.8790476231588706\n",
      "The 1-th  step is running\n",
      "the rmse of this step on train data is  0.14230368010966846\n",
      "The 2-th  step is running\n",
      "the rmse of this step on train data is  0.09620884732599529\n",
      "The 3-th  step is running\n",
      "the rmse of this step on train data is  0.0801263430192308\n",
      "The 4-th  step is running\n",
      "the rmse of this step on train data is  0.07184849200916447\n",
      "The 5-th  step is running\n",
      "the rmse of this step on train data is  0.06639183319158276\n",
      "The 6-th  step is running\n",
      "the rmse of this step on train data is  0.062399178741523835\n",
      "The 7-th  step is running\n",
      "the rmse of this step on train data is  0.05910024055994957\n",
      "The 8-th  step is running\n",
      "the rmse of this step on train data is  0.05656541567088465\n",
      "The 9-th  step is running\n",
      "the rmse of this step on train data is  0.0545543010104861\n",
      "The 10-th  step is running\n",
      "the rmse of this step on train data is  0.052691005015123984\n",
      "The 11-th  step is running\n",
      "the rmse of this step on train data is  0.05115914442783541\n",
      "The 12-th  step is running\n",
      "the rmse of this step on train data is  0.05007004361530988\n",
      "The 13-th  step is running\n",
      "the rmse of this step on train data is  0.04895133844904086\n",
      "The 14-th  step is running\n",
      "the rmse of this step on train data is  0.04816111082066557\n",
      "The 15-th  step is running\n",
      "the rmse of this step on train data is  0.04735111989809699\n",
      "The 16-th  step is running\n",
      "the rmse of this step on train data is  0.04669824972388296\n",
      "The 17-th  step is running\n",
      "the rmse of this step on train data is  0.04614029567648019\n",
      "The 18-th  step is running\n",
      "the rmse of this step on train data is  0.045670177347221334\n",
      "The 19-th  step is running\n",
      "the rmse of this step on train data is  0.04526352262213145\n",
      "The 20-th  step is running\n",
      "the rmse of this step on train data is  0.04490412609861553\n",
      "The 21-th  step is running\n",
      "the rmse of this step on train data is  0.04455905456927319\n",
      "The 22-th  step is running\n",
      "the rmse of this step on train data is  0.04424370820364638\n",
      "The 23-th  step is running\n",
      "the rmse of this step on train data is  0.04397747862904749\n",
      "The 24-th  step is running\n",
      "the rmse of this step on train data is  0.04375282018233025\n",
      "The 25-th  step is running\n",
      "the rmse of this step on train data is  0.043545480057010706\n",
      "The 26-th  step is running\n",
      "the rmse of this step on train data is  0.043375643308290315\n",
      "The 27-th  step is running\n",
      "the rmse of this step on train data is  0.04320025276126169\n",
      "The 28-th  step is running\n",
      "the rmse of this step on train data is  0.043070095923479434\n",
      "The 29-th  step is running\n",
      "the rmse of this step on train data is  0.042926540213943175\n",
      "The 30-th  step is running\n",
      "the rmse of this step on train data is  0.04279311922744525\n",
      "The 31-th  step is running\n",
      "the rmse of this step on train data is  0.04268623039793246\n",
      "The 32-th  step is running\n",
      "the rmse of this step on train data is  0.04259851489449008\n",
      "The 33-th  step is running\n",
      "the rmse of this step on train data is  0.04250533439725394\n",
      "The 34-th  step is running\n",
      "the rmse of this step on train data is  0.042432362554783436\n",
      "The 35-th  step is running\n",
      "the rmse of this step on train data is  0.04235021492074779\n",
      "The 36-th  step is running\n",
      "the rmse of this step on train data is  0.04228468228312443\n",
      "The 37-th  step is running\n",
      "the rmse of this step on train data is  0.04221521942180467\n",
      "The 38-th  step is running\n",
      "the rmse of this step on train data is  0.042153405194433814\n",
      "The 39-th  step is running\n",
      "the rmse of this step on train data is  0.042099859184693396\n",
      "The 40-th  step is running\n",
      "the rmse of this step on train data is  0.0420650895063924\n",
      "The 41-th  step is running\n",
      "the rmse of this step on train data is  0.04201566531327581\n",
      "The 42-th  step is running\n",
      "the rmse of this step on train data is  0.04197485457540366\n",
      "The 43-th  step is running\n",
      "the rmse of this step on train data is  0.041933034403432774\n",
      "The 44-th  step is running\n",
      "the rmse of this step on train data is  0.041900568515598216\n",
      "The 45-th  step is running\n",
      "the rmse of this step on train data is  0.04186922931268852\n",
      "The 46-th  step is running\n",
      "the rmse of this step on train data is  0.04183828352097069\n",
      "The 47-th  step is running\n",
      "the rmse of this step on train data is  0.04180781224538063\n",
      "The 48-th  step is running\n",
      "the rmse of this step on train data is  0.04178643016420755\n",
      "The 49-th  step is running\n",
      "the rmse of this step on train data is  0.041761100994846534\n",
      "total time: 669.693252\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/opt/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:46: DeprecationWarning: time.clock has been deprecated in Python 3.3 and will be removed from Python 3.8: use time.perf_counter or time.process_time instead\n"
     ]
    }
   ],
   "source": [
    "#gamma：为学习率\n",
    "#Lambda：正则参数\n",
    "#steps：迭代次数\n",
    "\n",
    "steps=50\n",
    "gamma=0.04\n",
    "Lambda=0.15\n",
    "\n",
    "#总的打分记录数目\n",
    "n_records = df_train.shape[0]\n",
    "\n",
    "star=time.clock()\n",
    "\n",
    "for step in range(steps):  \n",
    "    print ('The ' + str(step) + '-th  step is running' )\n",
    "    rmse_sum=0.0 \n",
    "            \n",
    "    #将训练样本打散顺序\n",
    "    kk = np.random.permutation(n_records)  \n",
    "    for j in range(n_records):  \n",
    "        #每次一个训练样本\n",
    "        line = kk[j]  \n",
    "        \n",
    "        uid = df_train.iloc[line]['user']\n",
    "        iid = df_train.iloc[line]['song']\n",
    "    \n",
    "        rating  = df_train.iloc[line]['score']\n",
    "                \n",
    "        #预测残差\n",
    "        eui = rating - svd_pred(uid, iid)  \n",
    "        #残差平方和\n",
    "        rmse_sum += eui**2  \n",
    "                \n",
    "        #随机梯度下降，更新\n",
    "        bu[uid] += gamma * (eui - Lambda * bu[uid])  \n",
    "        bi[iid] += gamma * (eui - Lambda * bi[iid]) \n",
    "                \n",
    "        temp = qi[iid]  \n",
    "        qi[iid] += gamma * (eui* pu[uid]- Lambda*qi[iid] )  \n",
    "        pu[uid] += gamma * (eui* temp - Lambda*pu[uid])  \n",
    "            \n",
    "    #学习率递减\n",
    "    gamma=gamma*0.93  \n",
    "    print (\"the rmse of this step on train data is \",np.sqrt(rmse_sum/n_records))  \n",
    "    \n",
    "end=time.clock()\n",
    "print('total time:',end-star)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 预测打分并推荐\n",
    "1. 找到用户未打过分的歌曲\n",
    "2. 对用户未打过分的歌曲进行预测打分\n",
    "3. 推荐前N个分数最高物品"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "#定义对指定用户推荐指定数量歌曲的函数\n",
    "def SVD_CF_recommend_items(uid,n_item):\n",
    "    \n",
    "    #初始化推荐歌曲及此歌曲的预测打分\n",
    "    recommend_scores={}\n",
    "    for i in df_train['song'].unique():\n",
    "        if i not in user_item_dict[uid]:\n",
    "            recommend_scores[i]=svd_pred(uid,i)\n",
    "            \n",
    "            \n",
    "    #将recommend_scores转化为dataframe，并按降序排列\n",
    "    recommend_scores=pd.DataFrame.from_dict(recommend_scores, orient='index', columns=['values']).reset_index()\n",
    "    recommend_scores=recommend_scores.rename(columns={'index':'recommend_song','values':'recommend_score'})\n",
    "    recommend_scores=recommend_scores.sort_values(by='recommend_score',ascending=False)\n",
    "    \n",
    "    recommend_item=recommend_scores[0:n_item]\n",
    "    \n",
    "    return recommend_item"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试（性能评估）\n",
    "选择评估指标（精确率、召回率、覆盖率）  \n",
    "\n",
    "\n",
    "\n",
    "令系统的用户集合为 U， R(u) 是根据用户在训练集上的行为给用户作出的推荐列表，而 T(u) 是用户在测试集上的行为列表。  \n",
    "推荐结果的精确率定义为：\n",
    "$$\n",
    "Precision=\\frac{\\sum_{u\\in U}|R(u)\\cap T(u)|}{\\sum_{u\\in U}|R(u)|}\n",
    "$$\n",
    "推荐结果的召回率定义为：\n",
    "$$\n",
    "Recall=\\frac{\\sum_{u\\in U}|R(u)\\cap T(u)|}{\\sum_{u\\in U}|T(u)|}\n",
    "$$\n",
    "\n",
    "推荐系统的覆盖率为：\n",
    "$$\n",
    "Coverage=\\frac{\\sum_{u\\in U}|R(u)|}{|I|}\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>user</th>\n",
       "      <th>song</th>\n",
       "      <th>play_count_total_x</th>\n",
       "      <th>score</th>\n",
       "      <th>play_count_total_y</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>15824</td>\n",
       "      <td>119b7c88d58d0c6eb051365c103da5caf817bea6</td>\n",
       "      <td>SOEQJBS12A8AE475A4</td>\n",
       "      <td>2477</td>\n",
       "      <td>0.002422</td>\n",
       "      <td>2477</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>7599</td>\n",
       "      <td>8b2f76211d04fa0f91b9f0c8134064b2968882c2</td>\n",
       "      <td>SOYRAHL12A6310D821</td>\n",
       "      <td>684</td>\n",
       "      <td>0.073099</td>\n",
       "      <td>684</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>7954</td>\n",
       "      <td>7bdfc45af7e15511d150e2acb798cd5e4788abf5</td>\n",
       "      <td>SOSCDWE12AB01823C4</td>\n",
       "      <td>523</td>\n",
       "      <td>0.028681</td>\n",
       "      <td>523</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>25905</td>\n",
       "      <td>7d4736c0c05264716e87d7fc825a535e0a01ba6d</td>\n",
       "      <td>SOJEVHC12A8C13C3E5</td>\n",
       "      <td>140</td>\n",
       "      <td>0.014286</td>\n",
       "      <td>140</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>35108</td>\n",
       "      <td>954469357b2434a20c76e940eca93185141b7f9b</td>\n",
       "      <td>SOXKDFJ12A6D4FA8F9</td>\n",
       "      <td>307</td>\n",
       "      <td>0.006515</td>\n",
       "      <td>307</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                           user                song  \\\n",
       "15824  119b7c88d58d0c6eb051365c103da5caf817bea6  SOEQJBS12A8AE475A4   \n",
       "7599   8b2f76211d04fa0f91b9f0c8134064b2968882c2  SOYRAHL12A6310D821   \n",
       "7954   7bdfc45af7e15511d150e2acb798cd5e4788abf5  SOSCDWE12AB01823C4   \n",
       "25905  7d4736c0c05264716e87d7fc825a535e0a01ba6d  SOJEVHC12A8C13C3E5   \n",
       "35108  954469357b2434a20c76e940eca93185141b7f9b  SOXKDFJ12A6D4FA8F9   \n",
       "\n",
       "       play_count_total_x     score  play_count_total_y  \n",
       "15824                2477  0.002422                2477  \n",
       "7599                  684  0.073099                 684  \n",
       "7954                  523  0.028681                 523  \n",
       "25905                 140  0.014286                 140  \n",
       "35108                 307  0.006515                 307  "
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 读取测试集数据\n",
    "df_test=pd.read_csv('triplet_dataset_sub_test.csv',index_col=0)\n",
    "df_test.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "#读取测试集上用户-歌曲索引表\n",
    "user_item_test_dict=pickle.load(open('user_item_test_dict.pkl','rb'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1推荐10首歌 n_recommend=10"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/opt/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:1: DeprecationWarning: time.clock has been deprecated in Python 3.3 and will be removed from Python 3.8: use time.perf_counter or time.process_time instead\n",
      "  \"\"\"Entry point for launching an IPython kernel.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3b4bb393138bba331e3dde43dfdc05554f05a743 is new user\n",
      "af3ee32357049dd96231238bd1b019e8142ee6aa is new user\n",
      "6a58f480d522814c087fd3f8c77b3f32bb161f9d is new user\n",
      "7875303e731b91b046ec6fbcd640e0b7d8499753 is new user\n",
      "9d17a429365653228049e8fe3d5968d4cd5dc6fe is new user\n",
      "467e0e46181933c7e1a936e513ca55fbab4edaed is new user\n",
      "e504626e4d38404e3928bda4b0f266cbd38c42d8 is new user\n",
      "total time: 6.164809000000105\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/opt/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:52: DeprecationWarning: time.clock has been deprecated in Python 3.3 and will be removed from Python 3.8: use time.perf_counter or time.process_time instead\n"
     ]
    }
   ],
   "source": [
    "star=time.clock()\n",
    "\n",
    "#初始化变量\n",
    "\n",
    "#推荐歌曲数目\n",
    "n_recommend=10\n",
    "\n",
    "#推荐的歌曲中玩家会播放的歌曲数量\n",
    "n_hit=0\n",
    "\n",
    "#总共推荐的次数\n",
    "total_recommend=0\n",
    "\n",
    "#推荐的歌曲数量\n",
    "total_recommend_unique=set()\n",
    "#rss_test=0\n",
    "\n",
    "#遍历测试集中每一位用户\n",
    "for u in df_test['user'].unique():\n",
    "    \n",
    "    #打印出新用户\n",
    "    if u not in df_train['user'].unique():\n",
    "        print(u,'is new user')\n",
    "        continue\n",
    "        \n",
    "    recommend_items=SVD_CF_recommend_items(u,n_recommend)\n",
    "    \n",
    "    #遍历没首推荐歌曲，判断并找出推荐命中的歌曲\n",
    "    for recommend_i in recommend_items[recommend_items.columns[0]]:\n",
    "        if recommend_i in user_item_test_dict[u]:\n",
    "            n_hit=n_hit+1\n",
    "        total_recommend_unique.add(recommend_i) \n",
    "    \n",
    "        #计算mse\n",
    "        #score_pre=user_item_score.loc[u][recommend_i]\n",
    "        #score_test=user_item_test_score.loc[u][recommend_i]\n",
    "        #rss_test=rss_test+(score_pre-score_test)**2\n",
    "        \n",
    "        \n",
    "    total_recommend=total_recommend+n_recommend\n",
    "            \n",
    "#精确率          \n",
    "precision=n_hit/total_recommend\n",
    "\n",
    "#召回率\n",
    "recall=n_hit/len(df_test['song'])\n",
    "\n",
    "#覆盖率\n",
    "coverage=len(total_recommend_unique)/len(df_train['song'].unique())\n",
    "#rmse=np.sqrt(rss_test/len(df_test['song']))\n",
    "\n",
    "end=time.clock()\n",
    "print('total time:',end-star)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "n_recommend"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.028551912568306012"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "precision"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.02785181236673774"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "recall"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.11875"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "coverage"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 推荐20首歌曲 n_recommend = 20"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/opt/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:1: DeprecationWarning: time.clock has been deprecated in Python 3.3 and will be removed from Python 3.8: use time.perf_counter or time.process_time instead\n",
      "  \"\"\"Entry point for launching an IPython kernel.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3b4bb393138bba331e3dde43dfdc05554f05a743 is new user\n",
      "af3ee32357049dd96231238bd1b019e8142ee6aa is new user\n",
      "6a58f480d522814c087fd3f8c77b3f32bb161f9d is new user\n",
      "7875303e731b91b046ec6fbcd640e0b7d8499753 is new user\n",
      "9d17a429365653228049e8fe3d5968d4cd5dc6fe is new user\n",
      "467e0e46181933c7e1a936e513ca55fbab4edaed is new user\n",
      "e504626e4d38404e3928bda4b0f266cbd38c42d8 is new user\n",
      "total time: 6.205688000000009\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/opt/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:52: DeprecationWarning: time.clock has been deprecated in Python 3.3 and will be removed from Python 3.8: use time.perf_counter or time.process_time instead\n"
     ]
    }
   ],
   "source": [
    "star=time.clock()\n",
    "\n",
    "#初始化变量\n",
    "\n",
    "#推荐歌曲数目\n",
    "n_recommend=20\n",
    "\n",
    "#推荐的歌曲中玩家会播放的歌曲数量\n",
    "n_hit=0\n",
    "\n",
    "#总共推荐的次数\n",
    "total_recommend=0\n",
    "\n",
    "#推荐的歌曲数量\n",
    "total_recommend_unique=set()\n",
    "#rss_test=0\n",
    "\n",
    "#遍历测试集中每一位用户\n",
    "for u in df_test['user'].unique():\n",
    "    \n",
    "    #打印出新用户\n",
    "    if u not in df_train['user'].unique():\n",
    "        print(u,'is new user')\n",
    "        continue\n",
    "        \n",
    "    recommend_items=SVD_CF_recommend_items(u,n_recommend)\n",
    "    \n",
    "    #遍历没首推荐歌曲，判断并找出推荐命中的歌曲\n",
    "    for recommend_i in recommend_items[recommend_items.columns[0]]:\n",
    "        if recommend_i in user_item_test_dict[u]:\n",
    "            n_hit=n_hit+1\n",
    "        total_recommend_unique.add(recommend_i) \n",
    "    \n",
    "        #计算mse\n",
    "        #score_pre=user_item_score.loc[u][recommend_i]\n",
    "        #score_test=user_item_test_score.loc[u][recommend_i]\n",
    "        #rss_test=rss_test+(score_pre-score_test)**2\n",
    "        \n",
    "        \n",
    "    total_recommend=total_recommend+n_recommend\n",
    "            \n",
    "#精确率          \n",
    "precision=n_hit/total_recommend\n",
    "\n",
    "#召回率\n",
    "recall=n_hit/len(df_test['song'])\n",
    "\n",
    "#覆盖率\n",
    "coverage=len(total_recommend_unique)/len(df_train['song'].unique())\n",
    "#rmse=np.sqrt(rss_test/len(df_test['song']))\n",
    "\n",
    "end=time.clock()\n",
    "print('total time:',end-star)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "n_recommend: 20\n",
      "precision: 0.025273224043715847\n",
      "recall: 0.049307036247334755\n",
      "coverage: 0.17875\n"
     ]
    }
   ],
   "source": [
    "print('n_recommend:',n_recommend)\n",
    "print('precision:',precision)\n",
    "print('recall:',recall)\n",
    "print('coverage:',coverage)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型比较\n",
    "使用精确率和召回率比较基于用户的协同过滤、基于物品的协同过滤、基于SVD的协同过滤性能"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_recommend: 20\n",
    "            svd item  user\n",
    " \n",
    "precision: 0.025 0.018 0.015 svd>item>user\n",
    "recall: 0.049  0.036 0.030   svd>item>user\n",
    "coverage: 0.178 0.93 0.418   item>user>svd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_recommend: 10\n",
    "            svd item  user\n",
    " \n",
    "precision: 0.027 0.022 0.018 svd\n",
    "recall: 0.028  0.021   0.018 svd\n",
    "coverage: 0.118 0.685 0.25   item\n",
    "    \n",
    "    \n",
    "在多数情况下svd的性能都优于其他"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
